// 这个文件是实现共享内存
// 因为共享内存被创建出来是映射到进程地址空间的 , 所以用户只需要拿到进程地址空间中被映射的起始虚拟地址
// 和共享内存的大小就能全部访问了
#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
#include "common.hpp"

//权限
const int gmode = 0666; 

// 共享内存
class Shm_m
{

//这里面的所有函数不暴露给外面使用 , 只有自己使用 
private:
    // 公共函数 , 共享内存帮助函数
    // 因为创建和获取只有选项不同 , 其余相同 !
    void Shm_mHelper(int shmflg)
    {
        // 在创建之前先要生成 key
        // shmget(key_t key , size_t size, int shmflg);
        _shmid = shmget(_key, SHM_MNUM, shmflg);
        if (_shmid > 0)
            std::cout << "shmget shmid [ " << _shmid << " ] success ! " << std::endl;
        else
            ERR_EXIT("shmget");
    }

    void Create()
    {
        std::cout << CREATER << " creating ..." << std::endl;
        Shm_mHelper(IPC_CREAT | IPC_EXCL | gmode);
    }

    // 获取共享内存 -- client 来完成
    void GetShm_m()
    {
        std::cout << USER << " geting ..." << std::endl;
        Shm_mHelper(IPC_CREAT);
    }

    // 删除前想要去挂接 , 即 : 去掉映射关系
    void DeleteAttach()
    {
        // int shmdt(const void *shmaddr);
        //  传入挂接后的起始虚拟地址
        int n = shmdt(_shm_addrr);
        if (n == 0)
        {
            std::cout << "shmdt shmid [ " << _shmid << " ] success !" << std::endl;
        }
        else
        {
            std::cout << "shmdt shmid [ " << _shmid << " ] fail ..." << std::endl;
        }
    }

    // 销毁共享内存
    void Destory()
    {
        // int shmctl(int shmid, int cmd, struct shmid_ds *buf);
        int n = shmctl(_shmid, IPC_RMID, nullptr); // 目前不用带回内核数据结构中的信息 !
        if (n != -1)
        {
            std::cout << "shmctl rm [ " << _shmid << " ] success !" << std::endl;
        }
        else
        {
            std::cout << "shmctl rm [ " << _shmid << " ] fail .... " << std::endl;
        }
    }

public:
    Shm_m(const std::string &usertype)
        : _shm_addrr(nullptr), _size(SHM_MNUM), _shmid(-1), _key(-1), _usertype(usertype)
    {
        // 创建 key
        _key = ftok(PATH, PRO_ID);
        if (_key < 0)
        {
            ERR_EXIT("ftok");
        }

        // 只让服务端进行创建 , 也就是 CREATER
        if (usertype == CREATER)
        {
            // 创建共享内存
            Create();
        }
        if (usertype == USER)
        {
            // 获取共享内存
            GetShm_m();
        }
    }
    ~Shm_m()
    {
        // 只让创建者删除共享内存
        if (_usertype == CREATER)
        {
            // 去挂接
            DeleteAttach();
            Destory();
        }
    }

    // 创建共享内存 -- server 来完成

    // 两个进程都是要进行挂接的
    //  进行挂接 (映射到进程的虚拟的地址空间中)
    void Attach()
    {
        // void *shmat(int shmid, const void *shmaddr, int shmflg);
        //  返回的是挂接的虚拟地址空间位置的虚拟起始地址
        _shm_addrr = shmat(_shmid, nullptr, 0);
        int id = getpid();
        if (_shm_addrr != (void *)-1)
        {
            std::cout << "shmat [ " << _shmid << " ] to pid [ " << id << " ] success !" << std::endl;
        }
        else
        {
            ERR_EXIT("shmat");
        }
    }

    // 虚拟地址
    void *VirtualAddar()
    {
        return _shm_addrr;
    }

private:
    void *_shm_addrr;      // 起始虚拟地址
    size_t _size;          // 共享内存的大小
    key_t _key;            // 给内核用的唯一标识符key
    int _shmid;            // 给我们用的共享内存标识符
    std::string _usertype; // 使用类型 , 即 : 目前谁使用
};